import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
import org.jruby.RubyModule;
import org.jruby.RubyObjectAdapter;
import org.jruby.RubySymbol;
import org.jruby.exceptions.RaiseException;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.BasicLibraryService;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.javasupport.JavaUtil;

public class RcovrtService implements BasicLibraryService {
  
  private static RubyObjectAdapter rubyApi;
  
  public boolean basicLoad(Ruby runtime) {
    RubyModule rcov = runtime.getOrCreateModule("Rcov");
    RubyModule rcov__ = runtime.defineModuleUnder("RCOV__", rcov);
    IRubyObject sl = runtime.getObject().getConstantAt("SCRIPT_LINES__");
    
    if (sl == null) {
        runtime.getObject().setConstant("SCRIPT_LINES__", RubyHash.newHash(runtime));
    }
    
    rubyApi = JavaEmbedUtils.newObjectAdapter();
    rcov__.defineAnnotatedMethods(RcovrtService.class);
    return true;
  }
  
  @JRubyMethod(name="reset_callsite", meta = true)
  public static IRubyObject resetCallsite(IRubyObject recv) {
    CallsiteHook hook = CallsiteHook.getCallsiteHook();
    if (hook.isActive()) {
      throw RaiseException.createNativeRaiseException(recv.getRuntime(),
        new RuntimeException("Cannot reset the callsite info in the middle of a traced run."));
    }
    return hook.resetDefsites();
  }
  
  @JRubyMethod(name="reset_coverage", meta = true)
  public static IRubyObject resetCoverage(IRubyObject recv) {
    CoverageHook hook = CoverageHook.getCoverageHook();
    if (hook.isActive()) {
      throw RaiseException.createNativeRaiseException(recv.getRuntime(),
        new RuntimeException("Cannot reset the coverage info in the middle of a traced run."));
    }
    return hook.resetCoverage(recv.getRuntime());
  }
  
  @JRubyMethod(name="remove_coverage_hook", meta = true)
  public static IRubyObject removeCoverageHook(IRubyObject recv) {
    return removeRcovHook(recv, CoverageHook.getCoverageHook());
  }
  
  @JRubyMethod(name="install_coverage_hook", meta = true)
  public static IRubyObject installCoverageHook(IRubyObject recv) {
    return installRcovHook(recv, CoverageHook.getCoverageHook());
  }
  
  /*
     TODO: I think this is broken. I'm not sure why, but recreating
     cover all the time seems bad.
  */
  @JRubyMethod(name="generate_coverage_info", meta = true)
  public static IRubyObject generateCoverageInfo(IRubyObject recv) {
    Ruby run = recv.getRuntime();
    RubyHash cover = (RubyHash)CoverageHook.getCoverageHook().getCover(run);
    RubyHash xcover = RubyHash.newHash(run);
    RubyArray keys = cover.keys();
    RubyArray temp;
    ThreadContext  ctx = run.getCurrentContext();
    for (int i=0; i < keys.length().getLongValue(); i++) {
      IRubyObject key = keys.aref(JavaUtil.convertJavaToRuby(run, Long.valueOf(i)));
      temp = ((RubyArray)cover.op_aref(ctx, key)).aryDup();
      xcover.op_aset(ctx,key, temp);
    }
    RubyModule rcov__ = (RubyModule) recv.getRuntime().getModule("Rcov").getConstant("RCOV__");
    
    if (rcov__.const_defined_p(ctx, RubySymbol.newSymbol(recv.getRuntime(), "COVER")).isTrue()) {
        rcov__.remove_const(ctx, recv.getRuntime().newString("COVER"));
    }
    rcov__.defineConstant( "COVER", xcover );
    
    return xcover;
  }
  
  @JRubyMethod(name="remove_callsite_hook", meta = true)
  public static IRubyObject removeCallsiteHook(IRubyObject recv) {
    return removeRcovHook( recv, CallsiteHook.getCallsiteHook() );
  }
  
  @JRubyMethod(name="install_callsite_hook", meta = true)
  public static IRubyObject installCallsiteHook(IRubyObject recv) {
    return installRcovHook( recv, CallsiteHook.getCallsiteHook() );
  }
  
  @JRubyMethod(name="generate_callsite_info", meta = true)
  public static IRubyObject generateCallsiteInfo(IRubyObject recv) {
    return CallsiteHook.getCallsiteHook().getCallsiteInfo( recv.getRuntime() ).dup();
  }
  
  @JRubyMethod(name="ABI", meta = true)
  public static IRubyObject getAbi(IRubyObject recv) {
    RubyArray ary = recv.getRuntime().newArray();
    ary.add(RubyFixnum.int2fix( recv.getRuntime(), 2L));
    ary.add(RubyFixnum.int2fix( recv.getRuntime(), 0L));
    ary.add(RubyFixnum.int2fix( recv.getRuntime(), 0L));
    return ary;
  }
  
  private static IRubyObject removeRcovHook(IRubyObject recv, RcovHook hook) {
    hook.setActive(false);
    recv.getRuntime().removeEventHook(hook);
    return recv.getRuntime().getFalse();
  }
  
  private static IRubyObject installRcovHook( IRubyObject recv, RcovHook hook ) {
    if (!hook.isActive()) {
      hook.setActive(true);
      recv.getRuntime().addEventHook(hook);
      return recv.getRuntime().getTrue();
    } else {
      return recv.getRuntime().getFalse();
    }
  }
}
